import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.ArrayList;

/**
 * 有效号码
 * 题目：验证给定的字符串是否可以解释为十进制数字。
 * <p>
 * 例如:
 * "0"=>true
 * " 0.1"=>true
 * "abc"=>false
 * "1 a"=>false
 * "2e10"=>true
 * " -90e3    "=>true
 * " 1e"=>false
 * "e3"=>false
 * " 6e-1"=>true
 * " 99e2.5"= false
 * "53.5e93"=>true
 * " --6 "=>false
 * "-+3"=>false
 * "95a54e53"=>false
 * <p>
 * 说明: 我们有意将问题陈述地比较模糊。在实现代码之前，你应当事先思考所有可能的情况。这里给出一份可能存在于有效十进制数字中的字符列表：
 * <p>
 * 数字 0-9
 * 指数 - "e"
 * 正/负号 - "+"/"-"
 * 小数点 - "."
 * 当然，在输入中，这些字符的上下文也很重要。
 * <p>
 * 来源：力扣（LeetCode-65）
 * 链接：https://leetcode-cn.com/problems/valid-number
 *
 * @author godfrey
 * @since 2020-10-26
 */
@DisplayName("有效数字")
public class ValidNumber extends BaseTest {

    @DisplayName("直接法-时间复杂度O(n)，空间复杂度O(1)")
    @ParameterizedTest
    @ValueSource(strings = {"0.1", "abc", "2e10", "4e+"})
    void isNumber(String s) {
        s = s.trim();

        boolean pointSeen = false;
        boolean eSeen = false;
        boolean numberSeen = false;
        boolean numberAfterE = true;
        for (int i = 0; i < s.length(); i++) {
            if ('0' <= s.charAt(i) && s.charAt(i) <= '9') {
                numberSeen = true;
                numberAfterE = true;
            } else if (s.charAt(i) == '.') {
                if (eSeen || pointSeen) {
                    System.out.println(false);
                    return;
                }
                pointSeen = true;
            } else if (s.charAt(i) == 'e') {
                if (eSeen || !numberSeen) {
                    System.out.println(false);
                    return;
                }
                numberAfterE = false;
                eSeen = true;
            } else if (s.charAt(i) == '-' || s.charAt(i) == '+') {
                if (i != 0 && s.charAt(i - 1) != 'e') {
                    System.out.println(false);
                    return;
                }
            } else {
                System.out.println(false);
                return;
            }
        }

        System.out.println(numberSeen && numberAfterE);
    }

    @DisplayName("自动机-时间复杂度O(n)，空间复杂度O(n)")
    @ParameterizedTest
    @ValueSource(strings = {"0.1", "abc", "2e10", "4e+"})
    void isNumber2(final String s) {
        int[][] transitionTable = new int[][]{
                {-1, 0, 3, 1, 2, -1},    // next states for state 0
                {-1, 8, -1, 1, 4, 5},     // next states for state 1
                {-1, -1, -1, 4, -1, -1},  // next states for state 2
                {-1, -1, -1, 1, 2, -1},   // next states for state 3
                {-1, 8, -1, 4, -1, 5},    // next states for state 4
                {-1, -1, 6, 7, -1, -1},   // next states for state 5
                {-1, -1, -1, 7, -1, -1},  // next states for state 6
                {-1, 8, -1, 7, -1, -1},   // next states for state 7
                {-1, 8, -1, -1, -1, -1}   // next states for state 8
        };
        int state = 0;
        for (int i = 0; i < s.length(); ++i) {
            final char ch = s.charAt(i);
            InputType inputType = InputType.INVALID;
            if (Character.isSpaceChar(ch)) {
                inputType = InputType.SPACE;
            } else if (ch == '+' || ch == '-') {
                inputType = InputType.SIGN;
            } else if (Character.isDigit(ch)) {
                inputType = InputType.DIGIT;
            } else if (ch == '.') {
                inputType = InputType.DOT;
            } else if (ch == 'e' || ch == 'E') {
                inputType = InputType.EXPONENT;
            }
            // Get next state from current state and input symbol
            state = transitionTable[state][inputType.ordinal()];
            // Invalid input
            if (state == -1) {
                System.out.println(false);
                return;
            }
        }
        // If the current state belongs to one of the accepting (final) states,
        // then the number is valid
        System.out.println(state == 1 || state == 4 || state == 7 || state == 8);
    }

    enum InputType {
        INVALID,    // 0
        SPACE,      // 1
        SIGN,       // 2
        DIGIT,      // 3
        DOT,        // 4
        EXPONENT,   // 5
        NUM_INPUTS  // 6
    }


    @DisplayName("责任链模式")
    @ParameterizedTest
    @ValueSource(strings = {"0.1", "abc", "2e10", "4e+"})
    void isNumber3(String s) {
        NumberValidate nv = new NumberValidator();
        System.out.println(nv.validate(s));
    }

    /**
     * 每个类都实现这个接口
     */
    interface NumberValidate {
        /**
         * 验证
         */
        boolean validate(String s);
    }

    /**
     * 责任链模式
     */
    abstract class NumberValidateTemplate implements NumberValidate {
        @Override
        public boolean validate(String s) {
            s = s.trim();
            if (s.isEmpty()) {
                return false;
            }
            if (s.startsWith("+") || s.startsWith("-")) {
                s = s.substring(1);
            }
            return doValidate(s);
        }

        protected abstract boolean doValidate(String s);
    }

    /**
     * 实现 doValidate 判断是否是整数
     */
    class IntegerValidate extends NumberValidateTemplate {
        @Override
        protected boolean doValidate(String integer) {
            if (integer.isEmpty()) {
                return false;
            }
            for (int i = 0; i < integer.length(); ++i) {
                if (!Character.isDigit(integer.charAt(i))) {
                    return false;
                }
            }
            return true;
        }
    }

    /**
     * 实现 doValidate 判断是否是科学计数法
     */
    class SienceFormatValidate extends NumberValidateTemplate {
        @Override
        protected boolean doValidate(String s) {
            s = s.toLowerCase();
            int pos = s.indexOf("e");
            if (pos == -1 || s.length() == 1) {
                return false;
            }

            String first = s.substring(0, pos);
            String second = s.substring(pos + 1);

            return validatePartBeforeE(first) && validatePartAfterE(second);
        }

        private boolean validatePartBeforeE(String first) {
            if (first.isEmpty()) {
                return false;
            }
            if (first.startsWith(" ") || first.endsWith(" ")) {
                return false;
            }

            NumberValidate integerValidate = new IntegerValidate();
            NumberValidate floatValidate = new FloatValidate();

            return integerValidate.validate(first) || floatValidate.validate(first);
        }

        private boolean validatePartAfterE(String second) {
            if (second.isEmpty()) {
                return false;
            }
            if (second.startsWith(" ") || second.endsWith(" ")) {
                return false;
            }

            NumberValidate integerValidate = new IntegerValidate();

            return integerValidate.validate(second);
        }
    }

    /**
     * 实现 doValidate 判断是否是小数
     */
    class FloatValidate extends NumberValidateTemplate {
        @Override
        protected boolean doValidate(String floatVal) {
            int pos = floatVal.indexOf(".");
            if (pos == -1 || floatVal.length() == 1) {
                return false;
            }

            NumberValidate nv = new IntegerValidate();
            String first = floatVal.substring(0, pos);
            String second = floatVal.substring(pos + 1, floatVal.length());

            return checkFirstPart(first) && checkFirstPart(second);
        }

        private boolean checkFirstPart(String first) {
            return "".equals(first) || checkPart(first);
        }

        private boolean checkPart(String part) {
            if (!Character.isDigit(part.charAt(0)) || !Character.isDigit(part.charAt(part.length() - 1))) {
                return false;
            }

            NumberValidate nv = new IntegerValidate();

            return nv.validate(part);
        }
    }

    /**
     * 定义一个执行者，我们把之前实现的各个类加到一个数组里，然后依次调用
     */
    class NumberValidator implements NumberValidate {

        private final ArrayList<NumberValidate> validators = new ArrayList<>();

        public NumberValidator() {
            addValidators();
        }

        private void addValidators() {
            NumberValidate nv = new IntegerValidate();
            validators.add(nv);

            nv = new FloatValidate();
            validators.add(nv);

            nv = new SienceFormatValidate();
            validators.add(nv);
        }

        @Override
        public boolean validate(String s) {
            for (NumberValidate nv : validators) {
                if (nv.validate(s)) {
                    return true;
                }
            }

            return false;
        }
    }
}
